package com.hulk.dryad.admin.security;

import com.hulk.dryad.admin.security.token.TokenAuthenticationFilter;
import com.hulk.dryad.admin.security.token.TokenLoginFilter;
import com.hulk.dryad.admin.security.token.TokenManager;
import com.hulk.dryad.manage.security.component.DryadPreAuthenticationChecks;
import com.hulk.dryad.manage.security.component.PermitAllSecurityUrlProperties;
import com.hulk.dryad.manage.security.handler.FormAuthenticationFailureHandler;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.security.authentication.AuthenticationEventPublisher;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsChecker;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;


/**
 * SpringSecurity配置
 *
 * @author hulk
 * @date 2020/3/18 10:54
 */
@Slf4j
@RequiredArgsConstructor
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

	private static final String LOGIN_PAGE = "/token/login";

	private static final String LOGIN_PROCESSING_URL = "/token/form";


    private final UserDetailsChecker dryadPreAuthenticationChecks = new DryadPreAuthenticationChecks();

    public final MessageSource messageSource;

    private final AuthenticationEventPublisher defaultAuthenticationEventPublisher;

    private final PermitAllSecurityUrlProperties permitAllSecurityUrlProperties;

    private final UserDetailsService adminSysUserDetailsService;

    private final AuthenticationEntryPoint dryadAuthExceptionEntryPoint;

    private final TokenManager adminTokenManager;



    @SneakyThrows
    @Override
    protected void configure(HttpSecurity httpSecurity) {

    	//FROM 配置
		FormLoginConfigurer<HttpSecurity> formLoginConfigurer = httpSecurity.formLogin();
		formLoginConfigurer.loginPage(LOGIN_PAGE).loginProcessingUrl(LOGIN_PROCESSING_URL)
                .failureHandler(authenticationFailureHandler()).and().logout()
                .logoutSuccessHandler((request, response, authentication) -> {
                    String referer = request.getHeader(HttpHeaders.REFERER);
                    response.sendRedirect(referer);
                }).deleteCookies("JSESSIONID").invalidateHttpSession(true);

        // 允许使用iframe 嵌套，避免swagger-ui 不被加载的问题
        ////禁用页面缓存，返回的都是json
        httpSecurity.headers().frameOptions().disable().cacheControl();;
        ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = httpSecurity
                .authorizeRequests();

        //放开一些接口的权限校验
		permitAllSecurityUrlProperties.getIgnoreUrls().forEach(url -> registry.antMatchers(url).permitAll());

        //其余的都需授权访问
        registry.anyRequest().authenticated();

        //开启模拟请求，比如API POST测试工具的测试，不开启时，API POST为报403错误
        httpSecurity.csrf().disable();
        //开启跨域访问
        httpSecurity.cors();

        //不使用默认退出，自定义退出
        httpSecurity.logout().disable();

        //全局不创建session
        httpSecurity.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

        //未授权时访问须授权的资源端点
        httpSecurity.exceptionHandling().authenticationEntryPoint(dryadAuthExceptionEntryPoint);


        // 权限filter
		httpSecurity.addFilter(new TokenLoginFilter(authenticationManager(), adminTokenManager));
		httpSecurity.addFilter(new TokenAuthenticationFilter(authenticationManager(), adminTokenManager, adminSysUserDetailsService));


    }

    @SneakyThrows
    @Override
    public void configure(AuthenticationManagerBuilder auth)  {
        auth.authenticationProvider(daoAuthenticationProvider());
        auth.authenticationEventPublisher(defaultAuthenticationEventPublisher);
    }


    @Bean
    public DaoAuthenticationProvider daoAuthenticationProvider() {
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
        daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
        daoAuthenticationProvider.setUserDetailsService(adminSysUserDetailsService);
        daoAuthenticationProvider.setPostAuthenticationChecks(dryadPreAuthenticationChecks);
        daoAuthenticationProvider.setMessageSource(messageSource);
        //daoAuthenticationProvider.setUserDetailsPasswordService(); //密码更新
        return daoAuthenticationProvider;
    }

    /**
     * 解决 无法直接注入 AuthenticationManager
     *
     */
    @Bean
    @Override
    @SneakyThrows
    public AuthenticationManager authenticationManagerBean()  {
        return super.authenticationManagerBean();
    }

    @Bean
    public AuthenticationFailureHandler authenticationFailureHandler() {
        return new FormAuthenticationFailureHandler();
    }
    /**
     * https://spring.io/blog/2017/11/01/spring-security-5-0-0-rc1-released#password-storage-updated
     * Encoded password does not look like BCrypt
     * @return PasswordEncoder
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }


}
